package com.lj.file.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import com.lj.common.exception.CommonException;
import com.lj.file.entity.FileDetail;
import com.lj.file.enums.UploadStatus;
import com.lj.file.param.InitChunkUploadParam;
import com.lj.file.param.UploadParam;
import com.lj.file.result.FileDetailInfoResult;
import com.lj.file.result.PlatformInfoResult;
import com.lj.file.service.FileDetailService;
import com.lj.file.service.FilePartDetailService;
import com.lj.file.service.FileService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.platform.FileStorage;
import org.dromara.x.file.storage.core.platform.LocalPlusFileStorage;
import org.dromara.x.file.storage.core.upload.FilePartInfo;
import org.dromara.x.file.storage.core.upload.MultipartUploadSupportInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author luojing
 * @since 2025/7/7 11:14
 */
@Service
@Slf4j
public class FileServiceImpl implements FileService {
    @Resource
    private FileDetailService fileDetailService;
    @Resource
    private FilePartDetailService partDetailService;

    @Resource
    private FileStorageService fileStorageService;

    @Override
    public List<PlatformInfoResult> platformInfo() {
        List<FileStorage> fileStorageList = fileStorageService.getFileStorageList();
        if (CollUtil.isEmpty(fileStorageList)) {
            return Collections.emptyList();
        }
        return fileStorageList.stream().map(fileStorage -> {
            PlatformInfoResult result = new PlatformInfoResult();
            result.setPlatform(fileStorage.getPlatform());
            return result;
        }).toList();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public FileDetailInfoResult upload(UploadParam param) throws IOException {
        // 上传,x-file内部会调用{@link save(FileInfo fileInfo)} 方法进行保存
        FileInfo fileInfo = fileStorageService.of(param.getInputStream())
                .setPlatform(StrUtil.isNotBlank(param.getPlatform()), param.getPlatform())
                .setOriginalFilename(param.getOriginalName())
                // 只有支持的图片才可以生成缩略图
                .thumbnail(supportThumbnail(param.getOriginalName()))
                .setPath(getPathByDate())
                .upload();
        // 存储成功返回给前端
        return FileDetailInfoResult.of(fileDetailService.toFileDetail(fileInfo));
    }

    private boolean supportThumbnail(String fileName) {
        return FileNameUtil.isType(fileName, ImgUtil.IMAGE_TYPE_GIF,
                ImgUtil.IMAGE_TYPE_JPG, ImgUtil.IMAGE_TYPE_JPEG,
                ImgUtil.IMAGE_TYPE_BMP, ImgUtil.IMAGE_TYPE_PNG,
                ImgUtil.IMAGE_TYPE_PSD);
    }

    private String getPathByDate() {
        return LocalDateTimeUtil.format(LocalDate.now(), "yyyy/MM/dd") + StrPool.SLASH;
    }

    @Override
    public FileDetailInfoResult initChunkUpload(InitChunkUploadParam param) {
        MultipartUploadSupportInfo supportInfo = fileStorageService.isSupportMultipartUpload(param.getPlatform());
        if (!supportInfo.getIsSupport()) {
            throw new CommonException("此平台不支持分片上传!");
        }
        FileInfo fileInfo = fileStorageService
                .initiateMultipartUpload()
                .setPlatform(StrUtil.isNotBlank(param.getPlatform()), param.getPlatform())
                .setPath(getPathByDate())
                .setOriginalFilename(param.getOriginalName())
                .setSize(param.getSize())
                .init();
        log.info("分片上传初始化成功: {}", fileInfo);
        return FileDetailInfoResult.of(fileDetailService.toFileDetail(fileInfo));
    }

    private static final ConcurrentHashMap<String, AtomicLong> CHUNK_SIZE_CACHE = new ConcurrentHashMap<>();

    @Override
    public FilePartInfo chunkUpload(InputStream inputStream, int chunkNumber, Long fileId) throws IOException {
        FileDetail detail = fileDetailService.getById(fileId);
        if (detail == null) {
            throw new CommonException("上传id错误，请先初始化分片上传");
        }
        FileInfo fileInfo = fileDetailService.toFileInfo(detail);
        // 本地上传的话，防止出现重复上传的问题
        FileStorage fileStorage;
        if ((fileStorage = fileStorageService.getFileStorage(detail.getPlatform())) instanceof LocalPlusFileStorage) {
            LocalPlusFileStorage localPlusFileStorage = (LocalPlusFileStorage) fileStorage;
            String fileKey = fileStorage.getFileKey(fileInfo);
            String parent = FileUtil.file(localPlusFileStorage.getAbsolutePath(fileKey)).getParent();
            File dir = FileUtil.file(parent, fileInfo.getUploadId());
            File part = FileUtil.file(dir, String.valueOf(chunkNumber));
            if (FileUtil.exist(part)) {
                return fileDetailService.toFilePartInfo(partDetailService.getPart(fileInfo.getUploadId(), chunkNumber));
            }
        }
        // 分片上传
        FilePartInfo upload = fileStorageService
                .uploadPart(fileInfo, chunkNumber, inputStream, (long) inputStream.available())
                .setProgressListener(new ChunkProgressListener(fileId, chunkNumber, "上传"))
                .upload();
        // 判断上传完成
        AtomicLong fileChunkSize = CHUNK_SIZE_CACHE.computeIfAbsent(fileInfo.getId(), key -> new AtomicLong(0));
        if (fileChunkSize.addAndGet(upload.getPartSize()) >= fileInfo.getSize()) {
            fileStorageService.completeMultipartUpload(fileInfo)
                    .setProgressListener(new ChunkProgressListener(fileId, chunkNumber, "合并"))
                    .complete();
            CHUNK_SIZE_CACHE.remove(fileInfo.getId());
        }
        return upload;
    }

    @Override
    public void download(Long id, OutputStream outputStream) {
        FileDetail detail = fileDetailService.getById(id);
        // 分片上传，没上传成功下载会有问题
        if (UploadStatus.INIT.equals(detail.getUploadStatus())) {
            throw new CommonException("分片上传的文件还没有上传完毕，无法下载!");
        }
        FileInfo fileInfo = fileDetailService.toFileInfo(detail);
        fileStorageService.download(fileInfo).outputStream(outputStream);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(List<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        List<FileDetail> detailList = fileDetailService.listByIds(ids);
        if (CollUtil.isEmpty(detailList)) {
            return;
        }
        fileDetailService.removeByIds(ids);
        for (FileDetail detail : detailList) {
            FileInfo fileInfo = fileDetailService.toFileInfo(detail);
            fileStorageService.delete(fileInfo);
            if (UploadStatus.INIT.equals(detail.getUploadStatus())) {
                // 如果还有分片没有上传完，删除已上传的分片
                fileStorageService.abortMultipartUpload(fileInfo).abort();
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void chunkAbort(Long fileId) {
        FileDetail detail = fileDetailService.getById(fileId);
        if (detail == null) {
            throw new CommonException("上传id错误，请先初始化分片上传");
        }
        fileDetailService.removeById(fileId);
        FileInfo fileInfo = fileDetailService.toFileInfo(detail);
        fileStorageService.abortMultipartUpload(fileInfo).abort();
    }
}
